using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Text;
using RideMe.AddIns.Interfaces;
using System.Diagnostics;
using System.Windows.Forms;
using System.Drawing;
using RideMe.AddIns.Console.Properties;

namespace RideMe.AddIns.Console
{
    /// <summary>
    /// This add-in provides access to the console for an application, allowing direct access to input commands and see output from the "script/console" for the app
    /// </summary>
    public class RichTextConsoleAddIn : IAddIn
    {
        #region Private Variables

        private ConsoleProcess _consoleProcess;
        private RichTextBox _consoleTextBox;
        private ToolStripMenuItem _consoleMenuItem;
        private Collection<ConsoleEntry> _entries = new Collection<ConsoleEntry>();
        private int _commandIndex = int.MinValue;
        private RideMe.AddIns.Events.UserInterfaceEventSource.DockControlCreatedEventArgs _dockControlCreatedEventArgs = null;

        #endregion

        #region Methods

        /// <summary>
        /// This builds the textbox used to display console output, and take in console input
        /// </summary>
        /// <param name="name"></param>
        private void CreateTextBox(string name)
        {
            _consoleTextBox = new RichTextBox();
            _consoleTextBox.Name = name;
            _consoleTextBox.Multiline = true;
            _consoleTextBox.BackColor = Color.White;
            _consoleTextBox.Visible = true;
            _consoleTextBox.ScrollBars = RichTextBoxScrollBars.Vertical;
            _consoleTextBox.KeyUp += new KeyEventHandler(_consoleTextBox_KeyUp);
        }

        /// <summary>
        /// This writes the specified data to the process
        /// </summary>
        /// <param name="data"></param>
        private void WriteToProcess(string data)
        {
            if (_consoleProcess != null && _consoleProcess.TargetProcess != null)
                _consoleProcess.TargetProcess.StandardInput.WriteLine(data.Replace(Environment.NewLine, string.Empty));
        }

        #endregion

        #region Events

        /// <summary>
        /// Handles a key release event from the text box
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void _consoleTextBox_KeyUp(object sender, KeyEventArgs e)
        {
            //Grab all commands filtered from the entry list
            Collection<ConsoleEntry> commands = ConsoleEntry.Filter(ConsoleEntry.ConsoleEntryType.COMMAND, _entries);
            //Enumerate the key pressed
            switch (e.KeyCode)
            {
                case Keys.Return:
                    //We are only looking for when we have entered a new line, which means there must be the line typed out, and the new line just started AT LEAST
                    if (_consoleTextBox.Lines.Length < 2)
                        return;
                    //Grab the last line
                    string lastLine = _consoleTextBox.Lines[_consoleTextBox.Lines.Length - 2];
                    //Ensure the console process is still there
                    if (_consoleProcess == null || _consoleProcess.TargetProcess == null || _consoleProcess.TargetProcess.HasExited)
                        return;
                    //Add the current text to the entries as a command
                    _entries.Add(new ConsoleEntry(lastLine, ConsoleEntry.ConsoleEntryType.COMMAND));
                    //Write it to the process
                    WriteToProcess(lastLine);
                    //Set the command index to int.MinValue so that we have reset our position in the command history
                    _commandIndex = int.MinValue;
                    //Reformat the text
                    ReFormatText(string.Empty);
                    break;
                case Keys.Up:
                    //Scroll back through the command history
                    if (_commandIndex == int.MinValue)
                        _commandIndex = commands.Count - 1;
                    else if ((_commandIndex - 1) < 0)
                        _commandIndex = commands.Count - 1;
                    else
                        _commandIndex -= 1;
                    ReFormatText(commands[_commandIndex].Text);
                    break;
                case Keys.Down:
                    //Scroll forward through the command history
                    if (_commandIndex == int.MinValue)
                        _commandIndex = commands.Count - 1;
                    else if ((_commandIndex + 1) >= commands.Count)
                        _commandIndex = 0;
                    else
                        _commandIndex += 1;
                    ReFormatText(commands[_commandIndex].Text);
                    break;
            }
        }

        /// <summary>
        /// This builds a formatted rich text box based on console input/output, then re-assigns the RTF from that to our visible console output window, ensuring
        /// there is minimal flickering, and that formatting is kept consistent
        /// </summary>
        /// <param name="text"></param>
        private void ReFormatText(string text)
        {
            if (_consoleTextBox == null)
                return;

            if (_consoleTextBox.InvokeRequired)
            {
                _consoleTextBox.Invoke(new MethodInvoker(
                    delegate
                    {
                        ReFormatText(text);
                    }));
            }
            else
            {
                //Lets create a rich text box
                RichTextBox richTextBox = new RichTextBox();
                //Empty the text
                richTextBox.Focus();
                richTextBox.Text = string.Empty;
                richTextBox.ResetText();
                lock (_entries)
                {
                    //Loop through adding each line with the proper formatting
                    foreach (ConsoleEntry entry in _entries)
                    {
                        //Grab the start index based on the length of the text already
                        int start = richTextBox.TextLength;
                        //Append the text (so long as it isn't a command), and select it
                        if (entry.Type != ConsoleEntry.ConsoleEntryType.COMMAND)
                        {
                            richTextBox.AppendText(entry.Text);
                            richTextBox.Select(start, entry.Text.Length);
                        }
                        //Set the formatting for the text
                        switch (entry.Type)
                        {
                            case ConsoleEntry.ConsoleEntryType.COMMAND:
                                //richTextBox.SelectionColor = Color.Blue;
                                break;
                            case ConsoleEntry.ConsoleEntryType.RESPONSE:
                                richTextBox.SelectionColor = Color.Green;
                                break;
                            case ConsoleEntry.ConsoleEntryType.ERROR:
                                richTextBox.SelectionColor = Color.Red;
                                break;
                        }
                        //Deselect all
                        richTextBox.DeselectAll();
                        //Add a new line
                        if(entry.Type != ConsoleEntry.ConsoleEntryType.COMMAND)
                            richTextBox.AppendText(Environment.NewLine);
                    }
                }
                //If we have text specified, display it as the current line
                if (!string.IsNullOrEmpty(text))
                    richTextBox.AppendText(text);
                //Set the RTF of our console text box to the newly formatted text box
                _consoleTextBox.Rtf = richTextBox.Rtf;
                //Scroll to the end
                _consoleTextBox.SelectionLength = 0;
                _consoleTextBox.SelectionStart = _consoleTextBox.TextLength;
                _consoleTextBox.SelectionColor = Color.Black;
                _consoleTextBox.ScrollToCaret();
                //Destroy the created text box
                richTextBox = null;
            }
        }

        /// <summary>
        /// Handles error output from the console process
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleProcess_ErrorReceived(object sender, DataReceivedEventArgs e)
        {
            _entries.Add(new ConsoleEntry(e.Data, ConsoleEntry.ConsoleEntryType.ERROR));
            ReFormatText(string.Empty);
        }

        /// <summary>
        /// Handles the console process completing
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleProcess_Completed(object sender, EventArgs e)
        {
            if (_consoleMenuItem.Owner.InvokeRequired)
            {
                _consoleMenuItem.Owner.Invoke(new MethodInvoker(
                    delegate
                    {
                        _consoleProcess_Completed(sender, e);
                    }));
            }
            else
            {
                _consoleMenuItem.Tag = false;
                if (_consoleTextBox != null && _consoleTextBox.Parent != null)
                    _dockControlCreatedEventArgs.RemoveControl(_consoleTextBox);
                _consoleTextBox = null;
            }
        }

        /// <summary>
        /// Handles output from the console process
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleProcess_OutputReceived(object sender, DataReceivedEventArgs e)
        {
            _entries.Add(new ConsoleEntry(e.Data, ConsoleEntry.ConsoleEntryType.RESPONSE));
            ReFormatText(string.Empty);
        }

        /// <summary>
        /// Handles dock control creation to allow the add-in to setup its own controls
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserInterfaceEvents_BottomDockControlCreated(object sender, RideMe.AddIns.Events.UserInterfaceEventSource.DockControlCreatedEventArgs e)
        {
            _dockControlCreatedEventArgs = e;
        }

        /// <summary>
        /// This handles the dock control being closed off
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void dockControl_Closed(object sender, EventArgs e)
        {
            WriteToProcess("exit");
        }

        /// <summary>
        /// Handles menu creation to allow the add-in to setup its own menus
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UserInterfaceEvents_MenuCreated(object sender, RideMe.AddIns.Events.UserInterfaceEventSource.MenuCreatedEventArgs e)
        {
            _consoleMenuItem = new ToolStripMenuItem("&Console");
            _consoleMenuItem.ShortcutKeys = Keys.Control | Keys.Shift | Keys.C;
            _consoleMenuItem.Tag = false;
            _consoleMenuItem.Enabled = false;
            _consoleMenuItem.Click += new EventHandler(_consoleMenuItem_Click);
            e.AddMenuItem("View", _consoleMenuItem);
        }

        /// <summary>
        /// Handles the clicking of the start/stop console menu item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _consoleMenuItem_Click(object sender, EventArgs e)
        {
            switch ((bool)_consoleMenuItem.Tag)
            {
                case true:
                    WriteToProcess("exit");
                    break;
                case false:
                    if (_consoleTextBox == null)
                    {
                        CreateTextBox("Console");
                        _dockControlCreatedEventArgs.AddControl(_consoleTextBox, ((System.Drawing.Image)(Resources.ResourceManager.GetObject("console"))), false, new EventHandler(dockControl_Closed));
                    }
                    _consoleProcess.StartConsole();
                    _consoleMenuItem.Tag = true;
                    if (_dockControlCreatedEventArgs != null)
                        _dockControlCreatedEventArgs.ShowControl(_consoleTextBox);
                    break;
            }
        }

        /// <summary>
        /// This handles the project closing, so that the console menu item can be disabled, and any running console can be closed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProjectEvents_ProjectClosed(object sender, RideMe.AddIns.Events.ProjectEventSource.FileEventArgs e)
        {
            _consoleMenuItem.Enabled = false;
            WriteToProcess("exit");
        }

        /// <summary>
        /// This handles the project opening, so that the console menu item can be enabled
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void ProjectEvents_ProjectOpened(object sender, RideMe.AddIns.Events.ProjectEventSource.FileEventArgs e)
        {
            _consoleMenuItem.Enabled = true;
        }

        #endregion

        #region IAddIn Members

        /// <summary>
        /// Returns the name of the add-in
        /// </summary>
        public string Name
        {
            get { return "Console Add-In"; }
        }

        /// <summary>
        /// Returns the author of the add-in
        /// </summary>
        public string Author
        {
            get { return "El Draper / Michael Leung"; }
        }

        /// <summary>
        /// Returns the description of the add-in
        /// </summary>
        public string Description
        {
            get { return "scipt/console window to interact with Active Record objects, etc."; }
        }

        /// <summary>
        /// This initializes the add-in and its resources
        /// </summary>
        /// <param name="context"></param>
        public void Init(IApplicationContext context)
        {
            _consoleProcess = new ConsoleProcess();
            _consoleProcess.OutputReceived += new EventHandler<DataReceivedEventArgs>(_consoleProcess_OutputReceived);
            _consoleProcess.Completed += new EventHandler(_consoleProcess_Completed);
            _consoleProcess.ErrorReceived += new EventHandler<DataReceivedEventArgs>(_consoleProcess_ErrorReceived);

            context.UserInterfaceEvents.MenuCreated += new EventHandler<RideMe.AddIns.Events.UserInterfaceEventSource.MenuCreatedEventArgs>(UserInterfaceEvents_MenuCreated);
            context.UserInterfaceEvents.BottomDockControlCreated += new EventHandler<RideMe.AddIns.Events.UserInterfaceEventSource.DockControlCreatedEventArgs>(UserInterfaceEvents_BottomDockControlCreated);

            context.ProjectEvents.ProjectOpened += new EventHandler<RideMe.AddIns.Events.ProjectEventSource.FileEventArgs>(ProjectEvents_ProjectOpened);
            context.ProjectEvents.ProjectClosed += new EventHandler<RideMe.AddIns.Events.ProjectEventSource.FileEventArgs>(ProjectEvents_ProjectClosed);
        }

        /// <summary>
        /// This tidies up any resources tied to the add-in
        /// </summary>
        public void Destroy()
        {
            if (!_consoleProcess.TargetProcess.HasExited)
                _consoleProcess.TargetProcess.Kill();
            _consoleTextBox = null;
            _consoleMenuItem = null;
        }

        #endregion
    }
}
